package org.example.manager;

import lombok.NoArgsConstructor;
import lombok.Setter;
import org.example.utils.RedisUtils;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 重写spring cache
 * 将其式使用redisson实现
 *
 * @author huhao
 */
@NoArgsConstructor
public class PlusSpringCacheManager implements CacheManager {

    /**
     * 是否允许缓存空值。默认为 true，表示允许将 null 值存储到缓存中。
     */
    @Setter
    private boolean allowNullValues = true;

    /**
     * 是否启用事务感知。默认为 true，表示在事务中操作缓存时会进行相应的事务管理。
     */
    @Setter
    private boolean transactionAware = true;

    /**
     * 缓存配置信息，key 为 cacheName, value 为 CacheConfig 对象
     */
    Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();

    /**
     * 缓存实例，key 为 cacheName, value 为 Cache 对象
     */
    ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();

    /**
     * 根据指定的缓存名称来获取对应的缓存实例。
     *
     * @param name 缓存名称，支持通过 '#' 分隔符指定额外的缓存配置参数（TTL、最大空闲时间、最大大小）。
     * @return 缓存实例，如果不存在且不是动态缓存模式，则返回 null。
     */
    @Override
    public Cache getCache(String name) {
        // 将缓存名称按 '#' 分割成字符串数组
        String[] array = StringUtils.delimitedListToStringArray(name, "#");
        // 取数组的第一个元素作为缓存名称
        name = array[0];
        // 从缓存实例映射中获取指定名称的缓存实例
        Cache cache = instanceMap.get(name);
        // 如果缓存实例存在，则直接返回
        if (cache != null) {
            return cache;
        }
        // 如果缓存实例不存在，则从配置映射中获取缓存配置，如果不存在则创建默认配置
        CacheConfig config = configMap.computeIfAbsent(name, k -> new CacheConfig());
        // 如果数组长度大于 1，取数组的第二个元素作为TTL
        if (array.length > 1) {
            config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
        }
        // 如果数组长度大于 2，取数组的第三个元素作为最大空闲时间
        if (array.length > 2) {
            config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
        }
        // 如果数组长度大于 3，取数组的第四个元素作为缓存的最大大小
        if (array.length > 3) {
            config.setMaxSize(Integer.parseInt(array[3]));
        }
        // 如果缓存配置的最大空闲时间、TTL 和最大大小都为 0，则创建基于 Map 的缓存实例
        if (config.getTTL() == 0 && config.getMaxIdleTime() == 0 && config.getMaxSize() == 0) {
            return createMap(name);
        } else {
            // 根据缓存名称和缓存配置创建基于 Map 的缓存实例
            return createMapCache(name, config);
        }
    }

    /**
     * 创建一个基于 Redis 的缓存实例。
     *
     * @param name 缓存的名称，将用作 Redis 键。
     * @return 返回一个缓存实例，如果缓存实例已存在，则返回已存在的实例；否则返回新创建的实例。
     */
    private Cache createMap(String name) {
        // 获取 Redis 客户端并创建一个新的 Redisson Map 实例
        RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
        // 使用 Redisson Map 实例创建一个新的 RedissonCache 实例
        Cache cache = new RedissonCache(map, allowNullValues);
        // 如果启用了事务感知，则对缓存实例进行装饰，添加事务支持
        if (transactionAware) {
            // 使用 TransactionAwareCacheDecorator 对缓存实例进行装饰，使其支持事务
            cache = new TransactionAwareCacheDecorator(cache);
        }
        // 尝试将缓存实例放入缓存实例映射中，如果键已存在，则返回旧值
        Cache oldCache = instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            return oldCache;
        } else {
            return cache;
        }
    }

    /**
     * 创建一个基于 Redis 的带最大大小的缓存实例。
     *
     * @param name   缓存的名称，用作 Redis 键。
     * @param config 缓存配置，包含缓存的最大大小等配置信息。
     * @return 返回缓存实例，如果缓存实例已存在，则返回已存在的实例；否则返回新创建的实例。
     */
    private Cache createMapCache(String name, CacheConfig config) {
        // 获取 Redis 客户端并创建一个新的 RedissonMapCache 实例
        RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
        // 使用 RedissonMapCache 实例和缓存配置创建一个新的 RedissonCache 实例
        Cache cache = new RedissonCache(map, config, allowNullValues);
        // 如果启用了事务感知，则对缓存实例进行装饰，添加事务支持
        if (transactionAware) {
            // 使用 TransactionAwareCacheDecorator 对缓存实例进行装饰，使其支持事务
            cache = new TransactionAwareCacheDecorator(cache);
        }
        // 尝试将缓存实例放入缓存实例映射中，如果键已存在，则返回旧值
        Cache oldCache = instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            // 如果键已存在，说明之前已经创建过缓存实例，使用旧的缓存实例
            return oldCache;
        } else {
            // 如果键不存在，设置缓存的最大大小
            map.setMaxSize(config.getMaxSize());
            // 返回缓存实例
            return cache;
        }
    }

    /**
     * 返回一个包含当前CacheManager管理的所有缓存名称的集合。
     *
     * @return 一个不可修改的集合，包含所有缓存的名称。
     */
    @Override
    public Collection<String> getCacheNames() {
        // 获取 configMap 的键集合
        // 这个键集合包含了所有缓存的名称
        Set<String> cacheNames = configMap.keySet();
        // 将键集合转换为不可修改的集合
        // 这样返回给调用者的集合就不能被修改
        return Collections.unmodifiableSet(cacheNames);
    }
}
